﻿using System;
using System.Diagnostics;
using System.Drawing;

namespace EvoBoost.ComputerVision.ViolaJones
{
	public class IntegralImage
	{
		private readonly Size size;

		private int[,] integralImage;

		public IntegralImage(Bitmap image)
		{
			if (image == null)
				throw new ArgumentNullException("image");

			this.size = image.Size;
			this.CalculateBrightnessDeviation(image);
			this.BuildIntegralImage(image);
		}

		public Size Size
		{
			get { return this.size; }
		}

		public double BrightnessDeviation;

		public int CalculateSum(Rectangle rectangle)
		{
			if (!new Rectangle(Point.Empty, this.size).Contains(rectangle))
				throw new ArgumentException("Given rectangle should be placed in image.", "rectangle");

			return CalculateSumNoCheck(rectangle);
		}

		internal int CalculateSumNoCheck(Rectangle rectangle)
		{
			Debug.Assert(new Rectangle(Point.Empty, this.size).Contains(rectangle));

			int result = 0;
			result += this.integralImage[rectangle.Right, rectangle.Bottom];
			result -= this.integralImage[rectangle.Left, rectangle.Bottom];
			result -= this.integralImage[rectangle.Right, rectangle.Top];
			result += this.integralImage[rectangle.Left, rectangle.Top];

			return result;
		}

		private void CalculateBrightnessDeviation(Bitmap image)
		{
			double sum = 0, sumOfSquares = 0;
			for (int i = 0; i < image.Width; ++i)
				for (int j = 0; j < image.Height; ++j)
				{
					double brightness = image.GetPixel(i, j).GetBrightness() * 255;
					sum += brightness;
					sumOfSquares += brightness * brightness;
				}

			int pixelCount = image.Width * image.Height;
			double variance = sumOfSquares / pixelCount - sum * sum / (pixelCount * pixelCount);
			Debug.Assert(variance >= 0);
			this.BrightnessDeviation = Math.Sqrt(variance);
		}

		private void BuildIntegralImage(Bitmap image)
		{
			byte[,] intensityImage = new byte[image.Width, image.Height];

			for (int i = 0; i < image.Width; ++i)
				for (int j = 0; j < image.Height; ++j)
					intensityImage[i, j] = (byte)(image.GetPixel(i, j).GetBrightness() * 255);

			this.integralImage = new int[image.Width + 1, image.Height + 1];

			for (int i = 1; i <= image.Width; ++i)
			{
				for (int j = 1; j <= image.Height; ++j)
				{
					int sum = intensityImage[i - 1, j - 1];
					sum += this.integralImage[i - 1, j];
					sum += this.integralImage[i, j - 1];
					sum -= this.integralImage[i - 1, j - 1];

					this.integralImage[i, j] = sum;
				}
			}
		}
	}
}